package com.gusto.engine.semsim.service.impl;

import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;

import com.gusto.engine.beans.model.Res;
import com.gusto.engine.semsim.exceptions.ConfigException;
import com.gusto.engine.semsim.exceptions.SimilarityException;
import com.gusto.engine.semsim.measures.ResourceSimilarity;
import com.gusto.engine.semsim.service.SimilarityService;
import com.gusto.engine.semsim.util.ConfigUtils;
import com.gusto.engine.semsim.util.SimilarityUtils;
import com.gusto.engine.storage.persistence.PersistencePlugin;
import com.opensymphony.oscache.base.Cache;
import com.opensymphony.oscache.general.GeneralCacheAdministrator;

/**
 * <p>A facade that will be the point of interaction 
 * between clients and the semantic similarity engine</p>
 * <p>It's used to : 
 * <ul>
 * 	<li>caching capabilities</li>
 *  <li>get the similarities</li>
 *  <li>configure the plugins : Persistence, ...</li>
 *  <li>management of the config file</li>
 *  <li>configure the resource factory</li>
 *  <li>contain the similarity measures</li>
 *  <li>get the neighborhood</li>
 * </ul>
 * @author amokrane.belloui@gmail.com
 */
public class SimilarityServiceImpl implements SimilarityService {
	
	private Logger log = Logger.getLogger(SimilarityServiceImpl.class);
	
	private GeneralCacheAdministrator cacheAdministrator;
	private Cache cache;
	
	private ConfigUtils configUtils;
	private SimilarityUtils similarityUtils;
	
	private Map<String, ResourceSimilarity> contextSimilarities;
	private PersistencePlugin persistencePlugin = null;
	
	public SimilarityServiceImpl() {
		super();
		
		Properties props = new Properties();
		props.put("cache.memory", true);
		props.put("cache.capacity", "10000");
		props.put("cache.unlimited.disk", true);
		props.put("cache.algorithm", "com.opensymphony.oscache.base.algorithm.LRUCache");
		cacheAdministrator = new GeneralCacheAdministrator(props); 
		this.cache = cacheAdministrator.getCache();
		
		this.configUtils = new ConfigUtils();
		this.similarityUtils = new SimilarityUtils();
	}
	
	public void destroy() {
		cacheAdministrator.destroy();
	}
	
	/* ################################################# */
	@Deprecated
	public void setResourceSimilarityMap(Map<String, ResourceSimilarity> resourceSimilarity) {
		this.contextSimilarities = resourceSimilarity;
	}
	
	public void setContextSimilarities(Map<String, ResourceSimilarity> resourceSimilarity) {
		this.contextSimilarities = resourceSimilarity;
	}
	
	public void setPersistencePlugin(PersistencePlugin persistencePlugin) {
		this.persistencePlugin = persistencePlugin;
	}
	
	/* ################################################# */
	
	public void checkDefinitionChanged() throws ConfigException {
		configUtils.loadProperties();
		Iterator<String> iter = contextSimilarities.keySet().iterator();
		while (iter.hasNext()) {
			String context = iter.next();
			boolean hasChanged = configUtils.checkDefinitionChanged(context, contextSimilarities.get(context));
			if (hasChanged) {
				log.debug("Context '" + context + "' has changed");
				// Clean in the persistence Plugin
				if (persistencePlugin != null) {
					persistencePlugin.cleanDistances(context);
				}
				// Flush Cache
				cache.flushAll(new Date());
			}
		}
		configUtils.saveProperties();
	}
	
	// ## Similarity #############################################################
	
	/**
	 * Only method that allows calculation of distances, 
	 * so that it can include plugin's for storing, sending, the information
	 * @param context
	 * @param resource1
	 * @param resource2
	 * @return similarity
	 * @throws SimilarityException
	 */
	public Double getSimilarity(String context, Res resource1, Res resource2) throws SimilarityException {
		
		if (resource1.toString().compareTo(resource2.toString()) < 0) {
			Res tmp = resource1;
			resource1 = resource2;
			resource2 = tmp;
		}
		
		String key = "sim." + context + "." + resource1 + "." + resource2;
		Double sim;
		try {
			sim = (Double) cache.getFromCache(key);
			log.debug("Load similarity from Cache");
		} 
		catch (com.opensymphony.oscache.base.NeedsRefreshException nre) {
		    try {
		    	ResourceSimilarity resSim = contextSimilarities.get(context);
				sim = similarityUtils.getSimilarity(context, resource1, resource2, persistencePlugin, resSim);
				cache.putInCache(key, sim);
				log.debug("Calculate similarity with no cache");
		    } 
		    catch (Exception ex) {
		    	sim = (Double) nre.getCacheContent();
		        cache.cancelUpdate(key);
		        log.debug("Load similarity from Cache after Exception in Calculating Similarity", ex);
		    }
		}
	    return sim;
	}
	
	// ## Params #############################################################
	
	public void setCONFIG_FILE(String config_file) {
		configUtils.setConfigFile(config_file);
		log.info("Config file set to : " + config_file);
	}
	
	public String getCONFIG_FILE() {
		return configUtils.getConfigFile();
	}
	
	public void setCACHE_SIZE(String cache_size) {
		cacheAdministrator.setCacheCapacity(Integer.parseInt(cache_size));
		log.info("Cache size set to : " + this.cache.getCapacity());
	}
	
	public String getCACHE_SIZE() {
		return this.cache.getCapacity() + "";
	}
	
}
